/*
//按键消抖   高效算法 不用delay()
//一个一个扫描连续是1  弹起    连续是0 按下 不规则01——抖动
#include <reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY1 = P2^4;
sbit KEY2 = P2^5;
sbit KEY3 = P2^6;
sbit KEY4 = P2^7;

unsigned char code LedChar[] = {  //数码管显示字符转换表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
bit KeySta = 1;  //当前按键状态 默认抬起

void main()
{
    bit backup = 1;  //按键值备份，保存前一次的扫描值 默认前一次是抬起
    unsigned char cnt = 0;  //按键计数，记录按键按下的次数
    
    EA = 1;       //使能总中断
    ENLED = 0;    //选择数码管DS1进行显示
    ADDR3 = 1;
    ADDR2 = 0;
    ADDR1 = 0;
    ADDR0 = 0;
    TMOD = 0x01;  //设置T0为模式1
    TH0  = 0xF8;  //为T0赋初值0xF8CD，定时2ms   X*12/11059200=0.002  X=1843 65536-1843=63693  0xF8CD
    TL0  = 0xCD;
    ET0  = 1;     //使能T0中断
    TR0  = 1;     //启动T0
    P2 = 0xF7;    //P2.3置0，即KeyOut1输出低电平
    P0 = LedChar[cnt];   //显示按键次数初值
	
    while (1)
    {
        if (KeySta != backup)  //当前值与前次值不相等说明此时按键有动作
        {
            if (backup == 0)   //如果前次值为0，则说明当前是弹起动作
            {
                cnt++;         //按键次数+1
                if (cnt >= 10)
                {              //只用1个数码管显示，所以加到10就清零重新开始  0-9循环
                    cnt = 0;
                }
                P0 = LedChar[cnt];  //计数值显示到数码管上
            }
            backup = KeySta;   //更新备份为当前值，以备进行下次比较
        }
    }
}
// T0中断服务函数，用于按键状态的扫描并消抖 
void InterruptTimer0() interrupt 1
{
    static unsigned char keybuf = 0xFF;  //扫描缓冲区，保存一段时间内的扫描值
    
    TH0 = 0xF8;  //重新加载初值
    TL0 = 0xCD;
    keybuf = (keybuf<<1) | KEY4;  //缓冲区左移一位，并将当前扫描值移入最低位
    if (keybuf == 0x00)
    {   //连续8次扫描值都为0，即16ms内都只检测到按下状态时，可认为按键已按下
        KeySta = 0;
    }
    else if (keybuf == 0xFF)
    {   //连续8次扫描值都为1，即16ms内都只检测到弹起状态时，可认为按键已弹起
        KeySta = 1;
    }
    else
    {}  //其它情况则说明按键状态尚未稳定，则不对KeySta变量值进行更新
}
*/




/*
//矩阵按键  并消抖
#include <reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY_IN_1  = P2^4;
sbit KEY_IN_2  = P2^5;
sbit KEY_IN_3  = P2^6;
sbit KEY_IN_4  = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;

unsigned char code LedChar[] = {  //数码管显示字符转换表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char KeySta[4][4] = {  //全部矩阵按键的当前状态
    {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}
};

void main()
{
    unsigned char i, j;
    unsigned char backup[4][4] = {  //按键值 备份，保存前一次的值
        {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}
    };
    
    EA = 1;       //使能总中断
    ENLED = 0;    //选择数码管DS1进行显示
    ADDR3 = 1;
    ADDR2 = 0;
    ADDR1 = 0;
    ADDR0 = 0;
    TMOD = 0x01;  //设置T0为模式1
    TH0  = 0xFC;  //为T0赋初值0xFC67，定时1ms
    TL0  = 0x67;
    ET0  = 1;     //使能T0中断
    TR0  = 1;     //启动T0
    P0 = LedChar[0];   //默认显示0
	
    while (1)
    {
        for (i=0; i<4; i++)  //循环检测4*4的矩阵按键
        {
            for (j=0; j<4; j++)  //i代表out行   j代表in列
            {
                if (backup[i][j] != KeySta[i][j])  //检测按键动作
                {
                    if (backup[i][j] != 0)         //按键按下时执行动作
                    {
                        P0 = LedChar[i*4+j];       //将编号显示到数码管
                    }
                    backup[i][j] = KeySta[i][j];   //更新前一次的备份值
                }
            }
        }
    }
}
// T0中断服务函数，扫描矩阵按键状态并消抖 
void InterruptTimer0() interrupt 1
{
    unsigned char i;
    static unsigned char keyout = 0;  //矩阵按键扫描输出索引
    static unsigned char keybuf[4][4] = {  //矩阵按键扫描缓冲区
        {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF},
        {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF}
    };
    
    TH0 = 0xFC;  //重新加载初值
    TL0 = 0x67;
    //将一行的4个按键值移入缓冲区
    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
    //消抖后更新按键状态
    for (i=0; i<4; i++)  //每行4个按键，所以循环4次
    {
        if ((keybuf[keyout][i] & 0x0F) == 0x00)
        {   //连续4次扫描值为0，即4*4ms内都是按下状态时，可认为按键已稳定的按下
            KeySta[keyout][i] = 0;
        }
        else if ((keybuf[keyout][i] & 0x0F) == 0x0F)
        {   //连续4次扫描值为1，即4*4ms内都是弹起状态时，可认为按键已稳定的弹起
            KeySta[keyout][i] = 1;
        }
    }
    //执行下一次的扫描输出
    keyout++;                //输出索引递增
    keyout = keyout & 0x03;  //索引值加到4即归零
    //相当于
    // if(keyout>=4)
    // {
    //     keyout=0;
    // }
    switch (keyout)          //根据索引，释放当前输出引脚，拉低下次的输出引脚
    {
        case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
        case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
        case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
        case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
        default: break;
    }
}
*/




/*
//简易加法计算器
#include <reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY_IN_1  = P2^4;
sbit KEY_IN_2  = P2^5;
sbit KEY_IN_3  = P2^6;
sbit KEY_IN_4  = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;

unsigned char code LedChar[] = {  //数码管显示字符转换表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = {  //数码管显示缓冲区
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表
    { 0x31, 0x32, 0x33, 0x26 }, //数字键1、数字键2、数字键3、向上键
    { 0x34, 0x35, 0x36, 0x25 }, //数字键4、数字键5、数字键6、向左键
    { 0x37, 0x38, 0x39, 0x28 }, //数字键7、数字键8、数字键9、向下键
    { 0x30, 0x1B, 0x0D, 0x27 }  //数字键0、ESC键、  回车键、 向右键
};
unsigned char KeySta[4][4] = {  //全部矩阵按键的当前状态
    {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}
};

void KeyDriver();

void main()
{
    EA = 1;       //使能总中断
    ENLED = 0;    //选择数码管进行显示
    ADDR3 = 1;
    TMOD = 0x01;  //设置T0为模式1
    TH0  = 0xFC;  //为T0赋初值0xFC67，定时1ms
    TL0  = 0x67;
    ET0  = 1;     //使能T0中断
    TR0  = 1;     //启动T0
    LedBuff[0] = LedChar[0];  //上电显示0
	
    while (1)
    {
        KeyDriver();   //调用按键驱动函数
    }
}
// 将一个无符号长整型的数字显示到数码管上，num-待显示数字 
void ShowNumber(unsigned long num)
{
    signed char i;
    unsigned char buf[6];
    
    for (i=0; i<6; i++)   //把长整型数转换为6位十进制的数组
    {
        buf[i] = num % 10;
        num = num / 10;
    }
    for (i=5; i>=1; i--)  //从最高位起，遇到0转换为空格，遇到非0则退出循环
    {
        if (buf[i] == 0)
            LedBuff[i] = 0xFF;
        else
            break;
    }
    for ( ; i>=0; i--)    //剩余低位都如实转换为数码管显示字符
    {
        LedBuff[i] = LedChar[buf[i]];
    }
}
// 按键动作函数，根据键码执行相应的操作，keycode-按键键码 
void KeyAction(unsigned char keycode)
{
    static unsigned long result = 0;  //用于保存运算结果
    static unsigned long addend = 0;  //用于保存输入的加数
    
    if ((keycode>=0x30) && (keycode<=0x39))  //输入0-9的数字
    {
        addend = (addend*10)+(keycode-0x30); //整体十进制左移，新数字进入个位
        ShowNumber(addend);    //运算结果显示到数码管
    }
    else if (keycode == 0x26)  //向上键用作加号，执行加法或连加运算
    {
        result += addend;      //进行加法运算
        addend = 0;
        ShowNumber(result);    //运算结果显示到数码管
    }
    else if (keycode == 0x0D)  //回车键，执行加法运算(实际效果与加号相同)
    {
        result += addend;      //进行加法运算
        addend = 0;
        ShowNumber(result);    //运算结果显示到数码管
    }
    else if (keycode == 0x1B)  //Esc键，清零结果
    {
        addend = 0;
        result = 0;
        ShowNumber(addend);    //清零后的加数显示到数码管
    }
}
//按键驱动函数，检测按键动作，调度相应动作函数，需在主循环中调用 
void KeyDriver()
{
    unsigned char i, j;
    static unsigned char backup[4][4] = {  //按键值备份，保存前一次的值
        {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}
    };
    
    for (i=0; i<4; i++)  //循环检测4*4的矩阵按键
    {
        for (j=0; j<4; j++)
        {
            if (backup[i][j] != KeySta[i][j])    //检测按键动作
            {
                if (backup[i][j] != 0)           //按键按下时执行动作
                {
                    KeyAction(KeyCodeMap[i][j]); //调用按键动作函数
                }
                backup[i][j] = KeySta[i][j];     //刷新前一次的备份值
            }
        }
    }
}
// 按键扫描函数，需在定时中断中调用，推荐调用间隔1ms 
void KeyScan()
{
    unsigned char i;
    static unsigned char keyout = 0;   //矩阵按键扫描输出索引
    static unsigned char keybuf[4][4] = {  //矩阵按键扫描缓冲区
        {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF},
        {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF}
    };

    //将一行的4个按键值移入缓冲区
    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
    //消抖后更新按键状态
    for (i=0; i<4; i++)  //每行4个按键，所以循环4次
    {
        if ((keybuf[keyout][i] & 0x0F) == 0x00)
        {   //连续4次扫描值为0，即4*4ms内都是按下状态时，可认为按键已稳定的按下
            KeySta[keyout][i] = 0;
        }
        else if ((keybuf[keyout][i] & 0x0F) == 0x0F)
        {   //连续4次扫描值为1，即4*4ms内都是弹起状态时，可认为按键已稳定的弹起
            KeySta[keyout][i] = 1;
        }
    }
    //执行下一次的扫描输出
    keyout++;                //输出索引递增
    keyout = keyout & 0x03;  //索引值加到4即归零
    switch (keyout)          //根据索引，释放当前输出引脚，拉低下次的输出引脚
    {
        case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
        case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
        case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
        case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
        default: break;
    }
}
//数码管动态扫描刷新函数，需在定时中断中调用 
void LedScan()
{
    static unsigned char i = 0;  //动态扫描的索引
    
    P0 = 0xFF;   //显示消隐
    switch (i)
    {
        case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
        case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
        case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
        case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
        case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
        case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
        default: break;
    }
}
// T0中断服务函数，用于数码管显示扫描与按键扫描 
void InterruptTimer0() interrupt 1
{
    TH0 = 0xFC;  //重新加载初值
    TL0 = 0x67;
    LedScan();   //调用数码管显示扫描函数
    KeyScan();   //调用按键扫描函数
}
*/






/*
//深度解析 训练   控制LEDS1数码管
#include<reg52.h>
sbit ADDR0=P1^0;
sbit ADDR1=P1^1;
sbit ADDR2=P1^2;
sbit ADDR3=P1^3;
sbit ENLED=P1^4;

sbit keyIn1=P2^4;
sbit keyIn2=P2^5;
sbit keyIn3=P2^6;
sbit keyIn4=P2^7;

unsigned char code ledchar[16]={
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};

void main()
{
    bit backup=1;//抬起
    unsigned char count=0;
    ENLED=0;
    ADDR3=1;
    ADDR0=1;
    ADDR1=0;
    ADDR2=0;
    P2=0xF7;
    P0=ledchar[count];

    while(1)
    {
        if(keyIn4!=backup)//1  0按下
        {
            if(backup==0)
            {
                count++;
                if(count>9)
                {
                    count=0;
                }
                P0=ledchar[count];
            }
            backup=keyIn4;//1 1
        }
    }
}
*/




/*
//消抖动，delay()
#include<reg52.h>
sbit ADDR0=P1^0;
sbit ADDR1=P1^1;
sbit ADDR2=P1^2;
sbit ADDR3=P1^3;
sbit ENLED=P1^4;

sbit keyIn1=P2^4;
sbit keyIn2=P2^5;
sbit keyIn3=P2^6;
sbit keyIn4=P2^7;

void delay();

unsigned char code ledchar[16]={
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};

void main()
{
    bit backup=1;//抬起
    unsigned char count=0;
    ENLED=0;
    ADDR3=1;
    ADDR0=1;
    ADDR1=0;
    ADDR2=0;
    P2=0xF7;
    P0=ledchar[count];

    while(1)
    {
        if(keyIn4!=backup)//1  0按下
        {
            delay();
            if(backup==0)
            {
                count++;
                if(count>9)
                {
                    count=0;
                }
                P0=ledchar[count];
            }
            backup=keyIn4;//1 1
        }
    }
}
void delay()
{
    unsigned int i=1000;
    while(i--);
}
*/



//在中断完成消抖
//用中断必用定时器   2ms检测一次    有T1   
#include<reg52.h>
sbit ADDR0=P1^0;
sbit ADDR1=P1^1;
sbit ADDR2=P1^2;
sbit ADDR3=P1^3;
sbit ENLED=P1^4;

sbit keyIn1=P2^4;
sbit keyIn2=P2^5;
sbit keyIn3=P2^6;
sbit keyIn4=P2^7;

bit keysta=1;

unsigned char code ledchar[16]={
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};

void main()
{
    bit backup=1;//抬起
    unsigned char count=0;
    ENLED=0;
    ADDR3=1;
    ADDR0=1;
    ADDR1=0;
    ADDR2=0;
    P2=0xF7;
    
    EA=1;
    TMOD=0x10;
    //x*12/11059200=0.002   X=1843   65536-1843=0xF8CD
    TH1=0xF8;
    TL1=0xCD;
    TR1=1;
    ET1=1;

    P0=ledchar[count];

    while(1)
    {
        if(keysta!=backup)//1  0按下
        {
            
            if(backup==0)
            {
                count++;
                if(count>9)
                {
                    count=0;
                }
                P0=ledchar[count];
            }
            backup=keysta;//1 1
        }
    }
}
void  InterruptTimer1() interrupt 3  //X*8+3=0x001B=27 X=3
{
    static unsigned char keybuf=0xFF;
    TH1=0xF8;
    TL1=0xCD;
    keybuf=(keybuf<<1) | keyIn4;
    if(keybuf==0x00)
    {
        keysta=0;
    }
    else if(keybuf==0xFF)
    {
        keysta=1;
    }
    else
    {}
}





